﻿שימוש במערכת יצירת הטפסים
==================

בעת יצירת טפסי HTML, אנו בדרך כלל רואים שאנו כותבים הרבה קוד תצוגה שחוזר על עצמו שקשה להשתמש בו שוב פעם בפרוייקט נוסף. לדוגמא, לכל שדה טקסט, אנו צריכים לשייך אותו עם תוית טקסט ולהציג שגיאות אימות נתונים במידה והם קיימים.
בכדי לשפר את השימוש החוזר בקודים אלו, אנו יכולים להשתמש במערכת יצירת הטפסים הקיימת במערכת ה Yii מגרסא 1.1.0.

תפיסה בסיסית
--------------

מערכת יצירת הטפסים משתמשת באובייקט של [CForm] כדי לייצג את המפרט הדרוש לייצוג טופס HTML, הכולל אילו מודלים קשורים לטופס, אילו שדות נתונים נמצאים בטופס, וכיצד להציג את כל הטופס.
מפתחים בעיקר צריכים ליצור ולהגדיר את האובייקט [CForm], לאחר מכן לקרוא למתודת התצוגה שלו בכדי להציג את הטופס.

מפרט שדות הטופס מסודרים במונחים של אלמנטים של אובייקט הטופס בהיררכיה. בראש ההיררכיה, נמצא האובייקט [CForm]. האובייקט הראשי מתחזק את התתים שבו בשני אוספים שונים: [CForm::buttons] ו [CForm::elements]. הראשון מכיל את האלמנטים המיוצגים ככפתורים (כמו כפתור שליחה, כפתור איפוס), בזמן שהשני מכיל את שדות הזנת הטקסט, טקסט סטטי, ותתי טפסים. תת-טופס הינו אובייקט של [CForm] הנמצא באוסף של האלמנטים ([CForm::elements]) בטופס אחר. הוא יכול להכיל מודל משלו, [CForm::buttons] ו [CForm::elements].

כשמשתמשים שולחים טופס, הנתונים שהוזנו בכל השדות בכל ההיררכיה של הטופס נשלחים, כולל את השדות הנמצאים בתתי-טפסים. [CForm] מספק מתודות נוחות לשימוש שבעזרתן ניתן לצרף נתונים מסויימים למאפייני המודלים המתאימים ולבצע אימות נתונים על גביהם.

יצירת טופס פשוט
----------------------

בדוגמא הבאה, אנו מציגים כיצד להשתמש במערכת יצירת הטפסים כדי ליצור טופס התחברות.

קודם כל, אנו כותבים את הפעולה של ההתחברות:

~~~
[php]
public function actionLogin()
{
    $model = new LoginForm;
    $form = new CForm('application.views.site.loginForm', $model);
    if($form-»submitted('login') && $form-»validate())
        $this-»redirect(array('site/index'));
    else
        $this-»render('login', array('form'=»$form));
}
~~~

בקוד למעלה, אנו יוצרים אובייקט של [CForm] תוך כדי שימוש במפרט המצביע לנתיב `application.views.site.loginForm` (הסבר לגבי זה בהמשך).
האובייקט [CForm] מקושר עם המודל `LoginForm` כפי שתואר [ביצירת מודל](/doc/guide/form.model).

כפי שמוצג בקוד, במידה והטופס נשלח וכל השדות אומתו ללא שגיאות, אנו נעביר את המשתמש לעמוד `site/index`. אחרת, אנו נציג שוב פעם את קובץ התצוגה `login` ביחד עם הטופס.

הנתיב `application.views.site.loginForm` מתייחס לקובץ PHP הנמצא תחת התיקיה `protected/views/site/loginForm.php`. הקובץ צריך להחזיר מערך המכיל את הגדרות הנחוצות עבור [CForm], כפי שמוצג בקוד הבא:

~~~
[php]
return array(
    'title'=»'Please provide your login credential',

    'elements'=»array(
        'username'=»array(
            'type'=»'text',
            'maxlength'=»32,
        ),
        'password'=»array(
            'type'=»'password',
            'maxlength'=»32,
        ),
        'rememberMe'=»array(
            'type'=»'checkbox',
        )
    ),

    'buttons'=»array(
        'login'=»array(
            'type'=»'submit',
            'label'=»'Login',
        ),
    ),
);
~~~

ההגדרות הינם מערך המכיל מפתחות וערכים המוגדרים למאפיינים של המחלקה [CForm]. המאפיינים החשובים ביותר להגדרה, כפי שכבר הזכרנו, [CForm::elements] ו [CForm::buttons]. כל אחד מהם מקבל מערך המגדיר רשימה של אלמנטים בטופס. אנו נסביר בהרחבה כיצד יש להגדיר אלמנטים של טופס בחלק הבא.

לבסוף, אנו כותבים את קובץ התצוגה `login`, שיכול להיות פשוט כפי שמוצג בקוד הבא,

~~~
[php]
«h1»Login«/h1»

«div class="form"»
«?php echo $form; ?»
«/div»
~~~

» Tip|טיפ: הקוד למעלה, `;echo $render` הוא זהה לקוד `;()echo $form-»render`. וזאת מכיוון שהאובייקט [CForm] מיישם את המתודה `toString__` אשר קוראת למתודה `()render` ומחזירה את התצוגה כסטרינג המייצג את אובייקט הטופס.


הגדרת אלמנטים בטופס
------------------------

בשימוש במערכת יצירת הטפסים, רוב העבודה שלנו משתנה מכתיבת קוד בקבצי התצוגה להגדרת אלמנטים בטופס. בחלק זה, אנו נתאר כיצד להגדיר את האלמנטים מאפיין [CForm::elements]. אנו לא נתאר אודות [CForm::buttons] מאחר והגדרותיו הם כמעט זהות ל [CForm::elements].

המאפיין [CForm::elements] מקבל מערך. כל אלמנט במערך מייצג אלמנט אחד בטופס שיכול להיות שדה נתונים, טקסט סטטי, או תת-טופס.

### הגדרת אלמנטים כשדות נתונים

אלמנט שדה נתונים בעיקר מכיל תוית, שדה נתונים, שדה עזרה ותצוגת שגיאה.
הוא חייב להיות מקושר למאפיין במודל מסויים. המפרט של אלמנט שדה נתונים מיוצג כאובייקט של [CFormInputElement]. הקוד הבא במערך [CForm::elements] מגדיר אלמנט שדה נתונים אחד:

~~~
[php]
'username'=»array(
    'type'=»'text',
    'maxlength'=»32,
),
~~~

קוד זה מציין שהמאפיין של המודל בשם `username`, ושדה הנתונים הוא מסוג `text` ושאורכו - `maxlength`  הוא 32. אנו יכולים לציין אפשרויות נוספות במערך למעלה כל עוד שהם מאפיינים שניתנים לכתיבה במחלקה [CFormInputElement]. לדוגמא, אנו יכולים להגדיר את האפשרות [hint|CFormInputElement::hint] המציגה טקסט עזרה עבור שדה זה, או שניתן להגדיר את האפשרות [items|CFormInputElement::items] במידה והשדה הוא תיבת בחירה, תיבת בחירה מרובה, רשימת כפתורי בחירה, רשימת כפתורי רדיו.

האפשרות [type|CFormInputElement::type] דורשת יותר תשומת לב מהשאר. אפשרות זו מגדירה את סוג שדה הנתונים לתצוגה. לדוגמא, הסוג `text` מעיד על כך שיש צורך להציג שדה טקסט רגיל; הסוג `password` אומר שיש צורך להציג שדה סיסמא. [CFormInputElement] מזהה את הסוגים המובנים הבאים:


 - text: שדה טקסט
 - hidden: שדה מוסתר
 - password: שדה סיסמא
 - textarea: תיבת טקסט
 - file: שדה קובץ
 - radio: שדה כפתור רדיו
 - checkbox: שדה כפתור בחירה
 - listbox: שדה בחירה מרובה
 - dropdownlist: שדה תיבת בחירה
 - checkboxlist: שדה רשימת כפתורי בחירה
 - radiolist: שדה רשימת כפתורי רדיו


מלבד הסוגים המובנים, האפשרות [type|CFormInputElement::type] יכולה לקבל שם של מחלקת וידג'ט או נתיב מקוצר עד אל הוידג'ט. המחלקה של הוידג'ט צריכה לירוש מהמחלקה [CInputWidget]. בעת התצוגה של האלמנט, אובייקט של הוידג'ט של השדה הנוכחי יווצר ויוצג. הוידג'ט יוגדר בהתבסס על המפרט הקיים באלמנט.

### הגדרת טקסט סטטי

בהרבה מקרים, מלבד שדות נתונים טופס מכיל קוד HTML שנועד לעיצוב הטופס. לדוגמא, יהיה צורך בקו אופקי בכדי להפריד בין חלקים בטופס; יש צורך בתמונה במקום מסויים בטופס בכדי לשפר את המראה החיצוני של הטופס. אנו יכולים להגדיר את קוד ה HTML הזה כטקסט סטטי באוסף האלמנטים של [CForm::elements]. בכדי לבצע זאת, אנו מגדירים אלמנט בתוך המאפיין [CForm::elements] בתור טקסט סטטי במיקום בו אנו רוצים להציג אותו בטופס. לדוגמא,

~~~
[php]
return array(
    'elements'=»array(
        ......
        'password'=»array(
            'type'=»'password',
            'maxlength'=»32,
        ),

        '«hr /»',

        'rememberMe'=»array(
            'type'=»'checkbox',
        )
    ),
    ......
);
~~~

בקוד המוצג למעלה, אנו מוסיפים קו אופקי בין השדה של הסיסמא - `password` לבין השדה של זכור אותי - `rememberMe`.

השימוש בטקסט סטטי הוא כשהטקסט המיקום שלו אינם רגילים. אם לכל שדה בטופס יהיה צורך בעיצוב באופן פרטני, יהיה צורך בלשנות את אופן התצוגה של הטופס, הסבר לפעולה זו יופיע בהמשך.

### הגדרת תתי-טפסים

תתי-טפסים נועדו בכדי לחלק טופס ארוך לכמה טפסים חלקים הקשורים אחד לשני לוגית. לדוגמא, אנו יכולים לחלק את טופס ההרשמה לשני תת-טפסים: פרטי התחברות ופרטי פרופיל.
כל תת-טופס חייב/לא חייב להיות משוייך למודל. בדוגמא של טופס ההרשמה, אם אנו שומרים את פרטי ההתחברות של המשתמש ואת פרטי הפרופיל של המשתמש בשני טבלאות שונות (ולכן שני מודלים נפרדים), לכן כל תת טופס יהיה משוייך למודל שלו. במידה ואנו שומרים את כל הפרטים באותה הטבלה, לכן לאף אחד מתתי-הטפסים לא יהיה מודל מאחר והם משותפים עם המודל של הטופס הראשי.

תת-טופס מיוצג כאובייקט של [CForm]. בכדי להגדיר תת-טופס, אנו צריכים להגדיר את המאפיין [CForm::elements] עם אלמנט שסוגו הוא `form`:

~~~
[php]
return array(
    'elements'=»array(
        ......
        'user'=»array(
            'type'=»'form',
            'title'=»'Login Credential',
            'elements'=»array(
                'username'=»array(
                    'type'=»'text',
                ),
                'password'=»array(
                    'type'=»'password',
                ),
                'email'=»array(
                    'type'=»'text',
                ),
            ),
        ),

        'profile'=»array(
            'type'=»'form',
            ......
        ),
        ......
    ),
    ......
);
~~~

כמו בהגדרת הטופס הראשי, אנו בעיקר צריכים להגדיר את המאפיין [CForm::elements] של תתי-הטפסים.
במידה ותת הטופס צריך להיות משוייך עם מודל, ניתן להגדיר את המאפיין [CForm::model] שלו גם כן.

לפעמים, אנו נרצה לייצג טופס המשתמש במחלקה אחרת ולא ברירת המחדל [CForm]. לדוגמא, כפי שנציג בעוד רגע בחלק זה, אנו יכולים להרחיב את המחלקה [CForm] בכדי להתאים אישית את אופן הצגת הטופס.
על ידי הגדרת סוג האלמנט כ `form`, תת הטופס אוטומטית מיוצג כאובייקט שהמחלקה שלו היא זהה למחלקה של טופס האב. אם נגדיר את סוג האלמנט למשהו כמו `XyzForm` (סטרינג המסתיים ב `Form`), אז תת-הטופס יהיה מיוצג על ידי האובייקט של המחלקה `XyzForm`.

גישה לאלמנטים בטופס
-----------------------

גישה לאלמנטים בטופס הינה פשוטה כגישה לאלמנטים מערך. המאפיין [CForm::elements] מחזיר אובייקט של [CFormElementCollection], היורש מהמחלקה [CMap] ומאפשר גישה לאלמנטים שבו כמערך רגיל. לדוגמא, בכדי לגשת לאלמנט `username` בדוגמא של טופס ההתחברות שהוצג, אנו יכולים להשתמש בקוד הבא:

~~~
[php]
$username = $form-»elements['username'];
~~~

ובכדי לגשת לאלמנט של `email` בדוגמא של טופס ההרשמה, אנו יכולים להשתמש ב

~~~
[php]
$email = $form-»elements['user']-»elements['email'];
~~~

מאחר ו [CForm] מיישם את הממשק של `array access` עבור המאפיין [CForm::elements], ניתן לפשט את הקוד למעלה בצורה הבאה:
 
~~~
[php]
$username = $form['username'];
$email = $form['user']['email'];
~~~


יצירת שרשור טפסים
----------------------

כבר הצגנו את התתי-טפסים. אנו קוראים לטופס עם תתי-טפסים טופס משורשר. בחלק זה, אנו משתמשים בטופס של הרשמת משתמשים בכדי להציג כיצד ליצור טופס משורשר המקושר לכמה מודלים.
אנו מניחים שפרטי המשתמשים מאוחסנים במודל `User`, בזמן שמידע אודות פרופיל המשתמש נמצא תחת המודל `Profile`.

אנו קודם יוצרים את פעולת ההרשמה `register` בצורה הבאה:

~~~
[php]
public function actionRegister()
{
    $form = new CForm('application.views.user.registerForm');
    $form['user']-»model = new User;
    $form['profile']-»model = new Profile;
    if($form-»submitted('register') && $form-»validate())
    {
        $user = $form['user']-»model;
        $profile = $form['profile']-»model;
        if($user-»save(false))
        {
            $profile-»userID = $user-»id;
            $profile-»save(false);
            $this-»redirect(array('site/index'));
        }
    }

    $this-»render('register', array('form'=»$form));
}
~~~

בקוד למעלה, אנו יוצרים את הטופס בעזרת שימוש בהגדרות הנמצאות ב `application.views.user.registerForm`.
לאחר השליחה של הטופס ואימות הנתונים עבר בהצלחה, אנו מנסים לשמור את המודלים של `User` ו `Profile`.
אנו שולפים את המודלים של המשתמש והפרופיל על ידי גישה למאפיין `model` תחת האובייקט של תת הטופס.

מאחר ואימות הנתונים נעשה כבר, אנו קוראים ל `$user-»save(false)` בכדי לדלג על האימות. אנו עושים את אותו הדבר עם המודל של הפרופיל.


לאחר מכן, אנו כותבים את קובץ הגדרות הטופס תחת הנתיב `protected/views/user/registerForm.php`:

~~~
[php]
return array(
    'elements'=»array(
        'user'=»array(
            'type'=»'form',
            'title'=»'Login information',
            'elements'=»array(
                'username'=»array(
                    'type'=»'text',
                ),
                'password'=»array(
                    'type'=»'password',
                ),
                'email'=»array(
                    'type'=»'text',
                )
            ),
        ),

        'profile'=»array(
            'type'=»'form',
            'title'=»'Profile information',
            'elements'=»array(
                'firstName'=»array(
                    'type'=»'text',
                ),
                'lastName'=»array(
                    'type'=»'text',
                ),
            ),
        ),
    ),

    'buttons'=»array(
        'register'=»array(
            'type'=»'submit',
            'label'=»'Register',
        ),
    ),
);
~~~

בקוד המוצג למעלה, בעת הגדרת כל תת-טופס, אנו מגדירים את המאפיין [CForm::title] השייך לו.
לוגיקת התצוגה ברירת המחדל של הטופס תעטוף כל תת-טופס בתוך מעטפת אשר תשתמש במאפיין זה בכדי להציג את הכותרת של הטופס.

לבסוף, אנו כותבים את קובץ התצוגה הפשוט של ההרשמה `register`:

~~~
[php]
«h1»הרשמה«/h1»

«div class="form"»
«?php echo $form; ?»
«/div»
~~~


התאמה אישית של תצוגה הטופס
------------------------

היתרון בשימוש של מערכת יצירת הטפסים היא ההפרדה מהלוגיקה (הגדרות הטופס נמצאות בקובץ נפרד) והתצוגה (מתודת [CForm::render]). כתוצאה מכך, אנו יכולים להתאים את התצוגה של הטופס באופן אישי על ידי דריסה של המתודה [CForm::render] או לספק קובץ תצוגה חלקי אשר מציג את הטופס. בשני האפשרויות ניתן להשאיר את הגדרות הטופס ללא שינוי ולהשתמש באותו הקוד בצורה קלה.

כשדורסים את המתודה [CForm::render], יש לרוץ על גבי האלמנטים של [CForm::elements] ו [CForm::buttons] ולקרוא למתודה [CFormElement::render] עבור כל כל אלמנט. לדוגמא,

~~~
[php]
class MyForm extends CForm
{
    public function render()
    {
        $output = $this-»renderBegin();

        foreach($this-»getElements() as $element)
            $output .= $element-»render();

        $output .= $this-»renderEnd();

        return $output;
    }
}
~~~

כמו כן אנו יכולים ליצור קובץ תצוגה `form_` בכדי להציג את הטופס:

~~~
[php]
«?php
echo $form-»renderBegin();

foreach($form-»getElements() as $element)
    echo $element-»render();

echo $form-»renderEnd();
~~~

בכדי להשתמש בקובץ התצוגה, ניתן פשוט לקרוא:

~~~
[php]
«div class="form"»
$this-»renderPartial('_form', array('form'=»$form));
«/div»
~~~

במידה ותצוגה כללית לא נראית טוב בטופס מסויים (לדוגמא, הטופס צריך דקורציה מיוחדת ולא רגילה עבור אלמנטים מסויימים), אנו יכולים לבצע את הפעולות הבאות בקובץ תצוגה:

~~~
[php]
אלמנטים של UI מורכבים כאן

«?php echo $form['username']; ?»

אלמנטים של UI מורכבים כאן

«?php echo $form['password']; ?»

אלמנטים של UI מורכבים כאן
~~~

בשיטה האחרונה,זה נראה שמערכת יצירת הטפסים לא מועילה לנו כל כך, אנו עדיין צריכים לכתוב כמות קוד דומה לטופס שאנו משתמשים בעת שימוש בקוד רגיל. למרות, שהטופס מוגדר בעזרת קובץ הגדרות בנפרד אשר עוזר למפתחים להתמקד יותר בלוגיקה.


«div class="revision"»$Id: form.builder.txt 1849 2010-03-01 15:54:34Z qiang.xue $«/div»